package com.medical.idempotent.interceptor;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.medical.idempotent.annotation.RepeatSubmit;
import com.medical.core.exception.BizException;
import com.medical.core.result.Result;
import com.medical.core.result.ResultCode;
import com.medical.redis.utils.RedisUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;


/**
 * 防重复提交（参考美团GTIS防重系统）
 */
@Aspect
@Slf4j
public class RepeatSubmitAspect {

    private static final String RESUBMIT_LOCK_PREFIX = "LOCK:RESUBMIT:";
    private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();

    @Setter
    private RedisUtil redisUtil;
    /**
     * 处理请求前
     * @param point 切点
     * @param repeatSubmit 注解
     * @throws Throwable
     */
    @Before("@annotation(repeatSubmit)")
    public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
        //如果注解部位0，则使用注解值
        long expire = repeatSubmit.timeUnit().toMillis(repeatSubmit.expire());
        if (expire < 1000) {
            throw new BizException("重复提交间隔时间不能小于1秒");
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String params = argsArrayToString(point.getArgs());
        //请求地址（作为存放cache的key）
        String url = request.getRequestURI();
        //获取到token
        String submitKey = "";
        submitKey = SecureUtil.md5(submitKey + ":" + params);
        // 唯一标识（指定key + url + 消息头）
        String cacheRepeatKey = RESUBMIT_LOCK_PREFIX + url + submitKey;
        if (redisUtil.get(cacheRepeatKey) != null) {
            String message = repeatSubmit.message();
            throw new BizException(message);
        } else {
            redisUtil.set(cacheRepeatKey, "", expire);
            KEY_CACHE.set(cacheRepeatKey);
        }
    }

    /**
     * 处理请求后
     * @param joinPoint 切点
     * @param repeatSubmit 注解
     * @param result 返回结果
     */
    @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object result) {
        if  (result instanceof Result<?> r) {
            try {
                //成功则删除Redis数据，保证在有效时间内无法重复提交
                if (r.getCode() == ResultCode.SUCCESS.getCode()) {
                    return ;
                }
                //删除Redis
                redisUtil.delete(KEY_CACHE.get());
            } finally {
                KEY_CACHE.remove();
            }
        }
    }

    /**
     * 拦截异常操作
     * @param joinPoint
     * @param repeatSubmit
     * @param e
     */
    @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
        //删除Redis
        redisUtil.delete(KEY_CACHE.get());
        KEY_CACHE.remove();
    }


    /**
     * 参数拼接
     * @param paramsArray
     * @return
     */
    private static String argsArrayToString(Object[] paramsArray) {
        StringJoiner params = new StringJoiner(" ");
        if (!ArrayUtil.isEmpty(paramsArray)) {
            for (Object o : paramsArray) {
                params.add(JSONUtil.toJsonStr(o));
            }
        }
        return params.toString();
    }


    public static void main(String[] args) {
        Map a = new HashMap<String, Object>();
        a.put("a", "sd");
        Map b = new HashMap<String, Object>();
        b.put("b", "dddd");
        List<Object> list = new ArrayList<>();
        list.add(a);
        list.add(b);
        System.out.println(argsArrayToString(list.toArray()));
    }
}
